using System;
using System.Collections.Generic;
using System.Text;

namespace OpenWaves.Text
{
    internal class BestFitASCIIFallbackBuffer : EncoderFallbackBuffer
    {
        private static readonly Dictionary<string, string> iso88591CharMap
            = new Dictionary<string, string>
                  {
                      {"\x00c2", "A"},
                      {"\x00c4", "A"},
                      {"\x00c6", "A"},
                      {"\x00c8", "E"},
                      {"\x00ca", "E"},
                      {"\x00cc", "I"},
                      {"\x00ce", "I"},
                      {"\x00d2", "O"},
                      {"\x00d4", "O"},
                      {"\x00d6", "O"},
                      {"\x00d8", "O"},
                      {"\x00da", "U"},
                      {"\x00dc", "U"},
                      {"\x00e0", "a"},
                      {"\x00e2", "a"},
                      {"\x00e4", "a"},
                      {"\x00e6", "ae"},/*a*/
                      {"\x00e8", "e"},
                      {"\x00ea", "e"},
                      {"\x00ec", "i"},
                      {"\x00ee", "i"},
                      {"\x00f0", "o"},
                      {"\x00f2", "o"},
                      {"\x00f4", "o"},
                      {"\x00f6", "o"},
                      {"\x00f8", "o"},
                      {"\x00fa", "u"},
                      {"\x00fc", "u"},
                      {"\x00c1", "A"},
                      {"\x00c3", "A"},
                      {"\x00c5", "A"},
                      {"\x00c7", "C"},
                      {"\x00c9", "E"},
                      {"\x00cb", "E"},
                      {"\x00cd", "I"},
                      {"\x00cf", "I"},
                      {"\x00d1", "N"},
                      {"\x00d3", "O"},
                      {"\x00d5", "O"},
                      {"\x00d9", "U"},
                      {"\x00db", "U"},
                      {"\x00dd", "Y"},
                      {"\x00df", "B"},
                      {"\x00e1", "a"},
                      {"\x00e3", "a"},
                      {"\x00e5", "a"},
                      {"\x00e7", "c"},
                      {"\x00e9", "e"},
                      {"\x00eb", "e"},
                      {"\x00ed", "i"},
                      {"\x00ef", "i"},
                      {"\x00f1", "n"},
                      {"\x00f3", "o"},
                      {"\x00f5", "o"},
                      {"\x00f9", "u"},
                      {"\x00fb", "u"},
                      {"\x00fd", "y"},
                      {"\x00ff", "y"},
                      {"\x0141", "L"},
                      {"\x0142", "l"},
                  };

        private readonly Encoding parentEncoding;
        private int charIndex = 1;
        private String fallbackString = String.Empty;

        public BestFitASCIIFallbackBuffer(BestFitASCIIEncoderFallback encoderFallback)
        {
            this.parentEncoding = encoderFallback.ParentEncoding;
        }

        public override int Remaining
        {
            get
            {
                // Any characters remaining?  If so, how many?
                if (this.charIndex < this.fallbackString.Length)
                    return this.fallbackString.Length - this.charIndex;

                return 0;
            }
        }

        public override bool Fallback(char charUnknown, int index)
        {
            // Since both fallback methods require normalizing a string, make a string out of our char
            String strUnknown = new String(charUnknown, 1);

            return this.Fallback(strUnknown);
        }

        public override bool Fallback(char charUnknownHigh, char charUnknownLow, int index)
        {
            // Since both fallback methods require normalizing a string, make a string out of our chars
            String strUnknown = new String(new[] {charUnknownHigh, charUnknownLow});

            return this.Fallback(strUnknown);
        }

        private bool Fallback(String unknownString)
        {
            // If they were falling back already they need to fail
            if (this.charIndex <= this.fallbackString.Length)
            {
                // Throw it, using our complete character
                this.charIndex = 1;
                this.fallbackString = String.Empty;
                throw new ArgumentException("Unexpected recursive fallback", "unknownString");
            }

            string normalizedString;
            try
            {
                // Normalize our character

                bool canFallback = iso88591CharMap.TryGetValue(unknownString, out normalizedString);
                if (!canFallback)
                {
                    normalizedString = unknownString.Normalize(NormalizationForm.FormKD);
                }

                //normalization hasn't changed anything
                if (normalizedString == unknownString)
                {
                    normalizedString = string.Empty;
                }
            }
                // Illegal Unicode can throw a normalization exception
            catch (ArgumentException)
            {
                // Allow the string to become a ? fallback
                normalizedString = string.Empty;
            }

            // Try encoding the normalized string
            this.fallbackString = this.parentEncoding.GetString(this.parentEncoding.GetBytes(normalizedString));

            // Any data from this fallback?
            if ((this.fallbackString.Length == 0) || (this.fallbackString[0] != normalizedString[0]))
            {
                // We didn't have data or didn't have the first character, so use ?
                this.fallbackString = "?";
            }

            this.charIndex = 0;

            return true;
        }


        /// <summary>
        /// Default version is overridden in EncoderReplacementFallback.cs
        /// </summary>
        /// <returns></returns>
        public override char GetNextChar()
        {
            // If we're above string length then return nothing
            if (this.charIndex >= this.fallbackString.Length)
            {
                // We use charIndex = length + 1 as a flag to tell us we're long past done.
                if (this.charIndex == this.fallbackString.Length)
                    this.charIndex++;
                return '\0';
            }

            // Return the character at our charIndex (& increment it)
            return this.fallbackString[this.charIndex++];
        }

        /// <summary>
        /// Caller needs to back up for some reason
        /// </summary>
        /// <returns></returns>
        public override bool MovePrevious()
        {
            // Back up only if we're within the string or just did the last char
            if (this.charIndex <= this.fallbackString.Length)
                this.charIndex--;

            // Return true if we were able to move previous
            return (this.charIndex >= 0 || this.charIndex < this.fallbackString.Length);
        }


        /// <summary>
        /// Clear the buffer
        /// </summary>
        public override void Reset()
        {
            this.fallbackString = String.Empty;
            this.charIndex = 1;
        }
    }
}